#pragma once

#include <iostream>
#include <pthread.h>
#include <queue>

#define NUM 5

template<typename T>
class ThreadPool{
  private:
    int thread_num;
    std::queue<T> task_queue;
    pthread_mutex_t lock;
    pthread_cond_t cond;

  public:
    ThreadPool(int _num = NUM):thread_num(_num)
    {
      pthread_mutex_init(&lock, nullptr);
      pthread_cond_init(&cond, nullptr);
    }

    static void* Routine(void *arg)
    {
      ThreadPool* self = (ThreadPool*)arg; 
      self->Detach();
      
      while(true){
        self->LockQueue();
        while(self->IsQueueEmpty()){
          self->Wait();
        }
        self->UnLockQueue();

        T t;
        self->Pop(t);
        t.Run();
      }
        
    }

    void InitThreadPool()
    {
      pthread_t tid; 
      for(int i = 0; i < thread_num; i++){
        pthread_create(&tid, nullptr, Routine, this);
      }

    }

    void Push(const T& in)
    {
      task_queue.push(in);
      WakeUp();
    }

    void Pop(T& out)
    {
      out = task_queue.front();
      task_queue.pop();
    }

    void Detach()
    {
      pthread_detach(pthread_self());
    }

    void LockQueue()
    {
      pthread_mutex_lock(&lock);
    }

    void UnLockQueue()
    {
      pthread_mutex_unlock(&lock);
    }

    void Wait()
    {
      pthread_cond_wait(&cond, &lock);
    }

    void WakeUp()
    {
      pthread_cond_signal(&cond);
    }

    bool IsQueueEmpty()
    {
      return task_queue.size() == 0;
    }

    bool IsQueueFulll()
    {
      return task_queue.size() == thread_num; 
    }

    ~ThreadPool()
    {
      pthread_mutex_destroy(&lock);
      pthread_cond_destroy(&cond);
    }
};
